1   /*
2    * Copyright (C) 2009 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.util.concurrent;
18  
19  import com.google.common.testing.TearDown;
20  import com.google.common.testing.TearDownStack;
21  
22  import junit.framework.TestCase;
23  
24  import java.lang.Thread.UncaughtExceptionHandler;
25  import java.util.concurrent.CountDownLatch;
26  import java.util.concurrent.Executor;
27  import java.util.concurrent.ExecutorService;
28  import java.util.concurrent.Executors;
29  import java.util.concurrent.TimeUnit;
30  import java.util.concurrent.TimeoutException;
31  
32  /**
33   * Unit test for {@link AbstractExecutionThreadService}.
34   *
35   * @author Jesse Wilson
36   */
37  public class AbstractExecutionThreadServiceTest extends TestCase {
38  
39    private final TearDownStack tearDownStack = new TearDownStack(true);
40    private final CountDownLatch enterRun = new CountDownLatch(1);
41    private final CountDownLatch exitRun = new CountDownLatch(1);
42  
43    private Thread executionThread;
44    private Throwable thrownByExecutionThread;
45    private final Executor exceptionCatchingExecutor = new Executor() {
46      @Override
47      public void execute(Runnable command) {
48        executionThread = new Thread(command);
49        executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
50          @Override
51          public void uncaughtException(Thread thread, Throwable e) {
52            thrownByExecutionThread = e;
53          }
54        });
55        executionThread.start();
56      }
57    };
58  
59    @Override protected final void tearDown() {
60      tearDownStack.runTearDown();
61    }
62  
63    public void testServiceStartStop() throws Exception {
64      WaitOnRunService service = new WaitOnRunService();
65      assertFalse(service.startUpCalled);
66  
67      service.startAsync().awaitRunning();
68      assertTrue(service.startUpCalled);
69      assertEquals(Service.State.RUNNING, service.state());
70  
71      enterRun.await(); // to avoid stopping the service until run() is invoked
72  
73      service.stopAsync().awaitTerminated();
74      assertTrue(service.shutDownCalled);
75      assertEquals(Service.State.TERMINATED, service.state());
76      executionThread.join();
77      assertNull(thrownByExecutionThread);
78    }
79  
80    public void testServiceStopIdempotence() throws Exception {
81      WaitOnRunService service = new WaitOnRunService();
82  
83      service.startAsync().awaitRunning();
84      enterRun.await(); // to avoid stopping the service until run() is invoked
85  
86      service.stopAsync();
87      service.stopAsync();
88      service.stopAsync().awaitTerminated();
89      assertEquals(Service.State.TERMINATED, service.state());
90      service.stopAsync().awaitTerminated();
91      assertEquals(Service.State.TERMINATED, service.state());
92  
93      executionThread.join();
94      assertNull(thrownByExecutionThread);
95    }
96  
97    public void testServiceExitingOnItsOwn() throws Exception {
98      WaitOnRunService service = new WaitOnRunService();
99      service.expectedShutdownState = Service.State.RUNNING;
100 
101     service.startAsync().awaitRunning();
102     assertTrue(service.startUpCalled);
103     assertEquals(Service.State.RUNNING, service.state());
104 
105     exitRun.countDown(); // the service will exit voluntarily
106     executionThread.join();
107 
108     assertTrue(service.shutDownCalled);
109     assertEquals(Service.State.TERMINATED, service.state());
110     assertNull(thrownByExecutionThread);
111 
112     service.stopAsync().awaitTerminated(); // no-op
113     assertEquals(Service.State.TERMINATED, service.state());
114     assertTrue(service.shutDownCalled);
115   }
116 
117   private class WaitOnRunService extends AbstractExecutionThreadService {
118     private boolean startUpCalled = false;
119     private boolean runCalled = false;
120     private boolean shutDownCalled = false;
121     private State expectedShutdownState = State.STOPPING;
122 
123     @Override protected void startUp() {
124       assertFalse(startUpCalled);
125       assertFalse(runCalled);
126       assertFalse(shutDownCalled);
127       startUpCalled = true;
128       assertEquals(State.STARTING, state());
129     }
130 
131     @Override protected void run() {
132       assertTrue(startUpCalled);
133       assertFalse(runCalled);
134       assertFalse(shutDownCalled);
135       runCalled = true;
136       assertEquals(State.RUNNING, state());
137 
138       enterRun.countDown();
139       try {
140         exitRun.await();
141       } catch (InterruptedException e) {
142         throw new RuntimeException(e);
143       }
144     }
145 
146     @Override protected void shutDown() {
147       assertTrue(startUpCalled);
148       assertTrue(runCalled);
149       assertFalse(shutDownCalled);
150       shutDownCalled = true;
151       assertEquals(expectedShutdownState, state());
152     }
153 
154     @Override protected void triggerShutdown() {
155       exitRun.countDown();
156     }
157 
158     @Override protected Executor executor() {
159       return exceptionCatchingExecutor;
160     }
161   }
162 
163   public void testServiceThrowOnStartUp() throws Exception {
164     ThrowOnStartUpService service = new ThrowOnStartUpService();
165     assertFalse(service.startUpCalled);
166 
167     service.startAsync();
168     try {
169       service.awaitRunning();
170       fail();
171     } catch (IllegalStateException expected) {
172       assertEquals("kaboom!", expected.getCause().getMessage());
173     }
174     executionThread.join();
175 
176     assertTrue(service.startUpCalled);
177     assertEquals(Service.State.FAILED, service.state());
178     assertTrue(thrownByExecutionThread.getMessage().equals("kaboom!"));
179   }
180 
181   private class ThrowOnStartUpService extends AbstractExecutionThreadService {
182     private boolean startUpCalled = false;
183 
184     @Override protected void startUp() {
185       startUpCalled = true;
186       throw new UnsupportedOperationException("kaboom!");
187     }
188 
189     @Override protected void run() {
190       throw new AssertionError("run() should not be called");
191     }
192 
193     @Override protected Executor executor() {
194       return exceptionCatchingExecutor;
195     }
196   }
197 
198   public void testServiceThrowOnRun() throws Exception {
199     ThrowOnRunService service = new ThrowOnRunService();
200 
201     service.startAsync();
202     try {
203       service.awaitTerminated();
204       fail();
205     } catch (IllegalStateException expected) {
206       executionThread.join();
207       assertEquals(thrownByExecutionThread, expected.getCause());
208     }
209     assertTrue(service.shutDownCalled);
210     assertEquals(Service.State.FAILED, service.state());
211     assertEquals("kaboom!", thrownByExecutionThread.getMessage());
212   }
213 
214   public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception {
215     ThrowOnRunService service = new ThrowOnRunService();
216     service.throwOnShutDown = true;
217 
218     service.startAsync();
219     try {
220       service.awaitTerminated();
221       fail();
222     } catch (IllegalStateException expected) {
223       executionThread.join();
224       assertEquals(thrownByExecutionThread, expected.getCause());
225     }
226 
227     assertTrue(service.shutDownCalled);
228     assertEquals(Service.State.FAILED, service.state());
229     assertEquals("kaboom!", thrownByExecutionThread.getMessage());
230   }
231 
232   private class ThrowOnRunService extends AbstractExecutionThreadService {
233     private boolean shutDownCalled = false;
234     private boolean throwOnShutDown = false;
235 
236     @Override protected void run() {
237       throw new UnsupportedOperationException("kaboom!");
238     }
239 
240     @Override protected void shutDown() {
241       shutDownCalled = true;
242       if (throwOnShutDown) {
243         throw new UnsupportedOperationException("double kaboom!");
244       }
245     }
246 
247     @Override protected Executor executor() {
248       return exceptionCatchingExecutor;
249     }
250   }
251 
252   public void testServiceThrowOnShutDown() throws Exception {
253     ThrowOnShutDown service = new ThrowOnShutDown();
254 
255     service.startAsync().awaitRunning();
256     assertEquals(Service.State.RUNNING, service.state());
257 
258     service.stopAsync();
259     enterRun.countDown();
260     executionThread.join();
261 
262     assertEquals(Service.State.FAILED, service.state());
263     assertEquals("kaboom!", thrownByExecutionThread.getMessage());
264   }
265 
266   private class ThrowOnShutDown extends AbstractExecutionThreadService {
267     @Override protected void run() {
268       try {
269         enterRun.await();
270       } catch (InterruptedException e) {
271         throw new RuntimeException(e);
272       }
273     }
274 
275     @Override protected void shutDown() {
276       throw new UnsupportedOperationException("kaboom!");
277     }
278 
279     @Override protected Executor executor() {
280       return exceptionCatchingExecutor;
281     }
282   }
283 
284   public void testServiceTimeoutOnStartUp() throws Exception {
285     TimeoutOnStartUp service = new TimeoutOnStartUp();
286 
287     try {
288       service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS);
289       fail();
290     } catch (TimeoutException e) {
291       assertTrue(e.getMessage().contains(Service.State.STARTING.toString()));
292     }
293   }
294 
295   private class TimeoutOnStartUp extends AbstractExecutionThreadService {
296     @Override protected Executor executor() {
297       return new Executor() {
298         @Override public void execute(Runnable command) {
299         }
300       };
301     }
302 
303     @Override
304     protected void run() throws Exception {
305     }
306   }
307 
308   public void testStopWhileStarting_runNotCalled() throws Exception {
309     final CountDownLatch started = new CountDownLatch(1);
310     FakeService service = new FakeService() {
311       @Override protected void startUp() throws Exception {
312         super.startUp();
313         started.await();
314       }
315     };
316     service.startAsync();
317     service.stopAsync();
318     started.countDown();
319     service.awaitTerminated();
320     assertEquals(Service.State.TERMINATED, service.state());
321     assertEquals(1, service.startupCalled);
322     assertEquals(0, service.runCalled);
323     assertEquals(1, service.shutdownCalled);
324   }
325 
326   public void testStop_noStart() {
327     FakeService service = new FakeService();
328     service.stopAsync().awaitTerminated();
329     assertEquals(Service.State.TERMINATED, service.state());
330     assertEquals(0, service.startupCalled);
331     assertEquals(0, service.runCalled);
332     assertEquals(0, service.shutdownCalled);
333   }
334 
335   public void testDefaultService() throws InterruptedException {
336     WaitOnRunService service = new WaitOnRunService();
337     service.startAsync().awaitRunning();
338     enterRun.await();
339     service.stopAsync().awaitTerminated();
340   }
341 
342   private class FakeService extends AbstractExecutionThreadService implements TearDown {
343 
344     private final ExecutorService executor = Executors.newSingleThreadExecutor();
345 
346     FakeService() {
347       tearDownStack.addTearDown(this);
348     }
349 
350     volatile int startupCalled = 0;
351     volatile int shutdownCalled = 0;
352     volatile int runCalled = 0;
353 
354     @Override protected void startUp() throws Exception {
355       assertEquals(0, startupCalled);
356       assertEquals(0, runCalled);
357       assertEquals(0, shutdownCalled);
358       startupCalled++;
359     }
360 
361     @Override protected void run() throws Exception {
362       assertEquals(1, startupCalled);
363       assertEquals(0, runCalled);
364       assertEquals(0, shutdownCalled);
365       runCalled++;
366     }
367 
368     @Override protected void shutDown() throws Exception {
369       assertEquals(1, startupCalled);
370       assertEquals(0, shutdownCalled);
371       assertEquals(Service.State.STOPPING, state());
372       shutdownCalled++;
373     }
374 
375     @Override protected Executor executor() {
376       return executor;
377     }
378 
379     @Override public void tearDown() throws Exception {
380       executor.shutdown();
381     }
382   }
383 }